IValueProvider物件透過一個ValueProviderFactory工廠來產生
Action方法綁定Model参数由實現IModelBinder的介面ModelBinder(DefaultModelBinder)物件來實現
在IModelBinder介面中有一個重要的方法object BindModel取得Model參數資料.
但在Http請求傳送參數極為複雜是如何將參數動態綁定在Action參數上呢?
最常見的Json參數透過POST Body傳到AP端,經由MVC BindModel來取得參數物件資料.
如下方資料.
{
"Key":"123",
"value":"",
"Adress":["test133","e2424"]
}
public class RootObject
{
public string Key { get; set; }
public string value { get; set; }
public List<string> Adress { get; set; }
}
網路上有個工具可方便使用Json字串取得
c#對應ModelJson to c# model
本篇就和大家分享這個機制是如何達成的
我有做一個可以針對於Asp.net MVC Debugger的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.
Model參數類型可能是一個簡單字串或者是一個值類型,也可能是一個複雜類型物件。
對於一個複雜類型物件,基於類型本身和物件成員元數據通過一個ModelMetadata類別來達成
某個成員又可能是一個複雜類型物件,通過ModelMetadata物件表示Model狀態,所以ModelMetadata(元數據)實際上具有一個樹形層次化的資料結構.
public class ModelMetadata
{
public Type ModelType { get; }
public virtual bool IsComplexType { get; }
public bool IsNullableValueType { get; }
public Type ContainerType { get; }
public object Model { get; set; }
public string PropertyName { get; }
public virtual Dictionary<string, object> AdditionalValues { get; }
protected ModelMetadataProvider Provider { get; set; }
public virtual IEnumerable<ModelMetadata> Properties
{
get
{
if (_properties == null)
{
IEnumerable<ModelMetadata> originalProperties = Provider.GetMetadataForProperties(Model, RealModelType);
_propertiesInternal = SortProperties(originalProperties.AsArray());
_properties = new ReadOnlyCollection<ModelMetadata>(_propertiesInternal);
}
return _properties;
}
}
}
在ModelMetadata類別中有幾個主要的屬性.
Provider(ModelMetadataProvider):存放當前物件下面一個ModelMetadataProvider資訊,ModelMetadataProvider主要是提供ModelMetadata是如何被產生(一般使用CachedDataAnnotationsModelMetadataProvider這個類別使用MemoryCache存放資訊)IEnumerable<ModelMetadata>:用來表示當前物件所使用屬性資訊ModelMetadata集合IsComplexType:判斷是否是複雜模型.ContainerType:父節點類別型態(可以看做樹狀結構,可當作存放根結點類型)ModelType:目前屬性或參數的類型.Model:綁定完使用的參數假如這邊有兩個類別Person,AddressInfo且一個Person可以擁有多個地址
這裡就會呈現一對多關係如下圖

就像大樹支點和葉子,這個屬性可能是葉子也可能是別人的支點.
public class Person
{
public int Age{ get; set; }
public string Name { get; set; }
public IEnumerable<AddressInfo> Address { get; set; }
}
public class AddressInfo
{
public string Name { get; set; }
}
上面類別關係圖就是簡單表示複雜模型
通過上面的介紹我們知道表示Model元數據ModelMetadata具有一個樹形層次結構
在每個ModelMetadata內部都有一個型別為IEnumerable<ModelMetadata>的Properties屬性來引用它的下級ModelMetadata,這就形成了一個無限巢狀的後設資料表示結構.

此圖可以表示ModelMetadata跟Person類別屬性的關係.
在上面介紹了ModelMetadata這個類別儲存了參數的各個資訊.
internal object BindSimpleModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
ValueProviderResult valueProviderResult
)
{
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
if (bindingContext.ModelType.IsInstanceOfType(valueProviderResult.RawValue))
{
return valueProviderResult.RawValue;
}
if (bindingContext.ModelType != typeof(string))
{
if (bindingContext.ModelType.IsArray)
{
object modelArray = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType);
return modelArray;
}
Type enumerableType = TypeHelpers.ExtractGenericInterface(bindingContext.ModelType, typeof(IEnumerable<>));
if (enumerableType != null)
{
object modelCollection = CreateModel(controllerContext, bindingContext, bindingContext.ModelType);
Type elementType = enumerableType.GetGenericArguments()[0];
Type arrayType = elementType.MakeArrayType();
object modelArray = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, arrayType);
Type collectionType = typeof(ICollection<>).MakeGenericType(elementType);
if (collectionType.IsInstanceOfType(modelCollection))
{
CollectionHelpers.ReplaceCollection(elementType, modelCollection, modelArray);
}
return modelCollection;
}
}
object model = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType);
return model;
}
private static object ConvertProviderResult(
ModelStateDictionary modelState,
string modelStateKey,
ValueProviderResult valueProviderResult,
Type destinationType
)
{
try
{
object convertedValue = valueProviderResult.ConvertTo(destinationType);
return convertedValue;
}
catch (Exception ex)
{
modelState.AddModelError(modelStateKey, ex);
return null;
}
}
透過ConvertProviderResult來將類型轉換成簡單模型綁定使用的參數實例.
在BindSimpleModel中依照下面幾個規則來做參數物件建立.
Array:如果此參數是陣列,判斷此陣列型別並利用ValueProviderResult.ConvertTo()建立陣列IEnumerable<>:如果此參數是IEnumerable<>集合,判斷此IEnumerable<>型別ValueProviderResult.ConvertTo()建立集合object:不是上面的型別就直接使用ValueProviderResult.ConvertTo()建立物件.
ConvertTo()方法在簡單模型物件建立起到一個很大的作用
在BindModel方法中有一個BindComplexModel方法是針對複雜模型產生的方法.
一開始先判斷ModelBindingContext.Model是否為Null如果是就會建立一個物件實例返回.
會依照下面機制判斷產生物件
Array產生一個相對應陣列集合IDictionary<,> and ICollection<>集合產生一個相對應陣列集合IEnumerable<>集合產生一個相對應陣列集合internal object BindComplexModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext
)
{
object model = bindingContext.Model;
Type modelType = bindingContext.ModelType;
if (model == null && modelType.IsArray)
{
Type elementType = modelType.GetElementType();
Type listType = typeof(List<>).MakeGenericType(elementType);
object collection = CreateModel(controllerContext, bindingContext, listType);
ModelBindingContext arrayBindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => collection, listType),
ModelName = bindingContext.ModelName,
ModelState = bindingContext.ModelState,
PropertyFilter = bindingContext.PropertyFilter,
ValueProvider = bindingContext.ValueProvider
};
IList list = (IList)UpdateCollection(controllerContext, arrayBindingContext, elementType);
if (list == null)
{
return null;
}
Array array = Array.CreateInstance(elementType, list.Count);
list.CopyTo(array, 0);
return array;
}
if (model == null)
{
model = CreateModel(controllerContext, bindingContext, modelType);
}
Type dictionaryType = TypeHelpers.ExtractGenericInterface(modelType, typeof(IDictionary<,>));
if (dictionaryType != null)
{
Type[] genericArguments = dictionaryType.GetGenericArguments();
Type keyType = genericArguments[0];
Type valueType = genericArguments[1];
ModelBindingContext dictionaryBindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType),
ModelName = bindingContext.ModelName,
ModelState = bindingContext.ModelState,
PropertyFilter = bindingContext.PropertyFilter,
ValueProvider = bindingContext.ValueProvider
};
object dictionary = UpdateDictionary(controllerContext, dictionaryBindingContext, keyType, valueType);
return dictionary;
}
Type enumerableType = TypeHelpers.ExtractGenericInterface(modelType, typeof(IEnumerable<>));
if (enumerableType != null)
{
Type elementType = enumerableType.GetGenericArguments()[0];
Type collectionType = typeof(ICollection<>).MakeGenericType(elementType);
if (collectionType.IsInstanceOfType(model))
{
ModelBindingContext collectionBindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType),
ModelName = bindingContext.ModelName,
ModelState = bindingContext.ModelState,
PropertyFilter = bindingContext.PropertyFilter,
ValueProvider = bindingContext.ValueProvider
};
object collection = UpdateCollection(controllerContext, collectionBindingContext, elementType);
return collection;
}
}
BindComplexElementalModel(controllerContext, bindingContext, model);
return model;
}
最後呼叫BindComplexElementalModel方法將剛剛建立值(model物件)透過ValueProvider把參數給值.
有分簡單綁定和複雜綁定,最後都還是會呼叫使用簡單綁定來值綁定給物件.
在BindProperty方法時填充子節點ModelMetadata的Model屬性.
GetPropertyValue透過(DefaultModelBinder)再次綁定物件動作如下
ModelMetadata是簡單模型就會把值填充給此次ModelMetadata.Model
ModelMetadata是複雜模型就建立一個物件後呼叫BindProperty直到找到最後的簡單模型.protected virtual void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
//...
IModelBinder propertyBinder = Binders.GetBinder(propertyDescriptor.PropertyType);
object originalPropertyValue = propertyDescriptor.GetValue(bindingContext.Model);
ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
propertyMetadata.Model = originalPropertyValue;
ModelBindingContext innerBindingContext = new ModelBindingContext()
{
ModelMetadata = propertyMetadata,
ModelName = fullPropertyKey,
ModelState = bindingContext.ModelState,
ValueProvider = bindingContext.ValueProvider
};
object newPropertyValue = GetPropertyValue(controllerContext, innerBindingContext, propertyDescriptor, propertyBinder);
propertyMetadata.Model = newPropertyValue;
//...
}
protected virtual object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
{
object value = propertyBinder.BindModel(controllerContext, bindingContext);
if (bindingContext.ModelMetadata.ConvertEmptyStringToNull && Equals(value, String.Empty))
{
return null;
}
return value;
}
MVC ModelBinding 使用到一個設計模式(組合模式),當我發現時覺得十分興奮.
因為在現實專案中我較少看到(組合模式),發輝良好的作用而在這個案例上發揮的淋漓盡致.
組合模式關係圖如下

組合模式基本上分為兩個部分葉(Left)和組件(component)他們都依賴於一個抽象,組件實現取得動作的抽象只為了獲得下面的葉,真正有動作只會在葉有動作
組合模式很適合用在樹狀的資料結構且需求對於葉和組件要做大量不一樣判斷.
在模型綁定中他依靠兩個東西完成上面說的依賴關聯
ModelBindingContext物件object CreateModel方法ModelBindingContext找到參數使用型別並利用ValueProvider給值,最後返回物件ModelBindingContext建立參數利用ValueProvider給值,往下繼續重複動作直到呼叫簡單模型綁定方法,就不會繼續往下呼叫object方法.這裡很巧妙的利用ModelBinderDictionary取得當前參數型別並取得相對應IModelBinder實現物件.
石頭大準備的Asp.net MVC Debugger的專案真的好用又貼心
可惜目前只能粗淺瀏覽不能深入閱讀,等鐵人賽完一定要好好拜讀! ![]()
沒問題 ![]()